
#include <fcntl.h>
#include <sys/ioctl.h>
#include <pthread.h>


#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/netlink.h>

#include "iAP2_AppStub.h"
#include "StringUtils.h"
#include "CIpodLibWrapper.h"
#include "CarPlayInterface.h"
ulog_define(iAP2AppStub, kLogLevelTrace, kLogFlags_Default, "iAP2AppStub", NULL);
#define ias_ucat()                  &log_category_from_name( iAP2AppStub )
#define ias_ulog( LEVEL, ... )      ulog( ias_ucat(), (LEVEL), __VA_ARGS__ )
#define ias_dlog( LEVEL, ... )      dlogc( ias_ucat(), (LEVEL), __VA_ARGS__ )

ulog_define(iAP2AppStubErr, kLogLevelTrace, kLogFlags_Default | kLogFlags_PrintError, "iAP2AppStubErr", NULL);
#define ias_ucat_err()                  &log_category_from_name( iAP2AppStubErr )
#define ias_ulog_err( LEVEL, ... )      ulog( ias_ucat_err(), (LEVEL), __VA_ARGS__ )
#define ias_dlog_err( LEVEL, ... )      dlogc( ias_ucat_err(), (LEVEL), __VA_ARGS__ )

//#define kUSBDetachProbeFile                 "/sys/bus/platform/devices/ci_hdrc.0/udc/ci_hdrc.0/state"
//#define kUSBRoleStatusFile                  "/sys/kernel/debug/ci_hdrc.0/role"

#define kUSBDetachProbeFile                 "/sys/devices/platform/soc/usbc0/otg_role"
#define kUSBRoleStatusFile                  "/sys/devices/platform/soc/usbc0/otg_role"

#define kBluetoothAddressFilePath           "/data/bluetooth/deviceinfo.ini"

typedef struct {
    char    deviceId[ 32 ];
    ipod_t  handle;
    int     transportType;
    int     started;

} iAP2Device;

typedef uint8_t     CarPlayConnectionType;

#define kCarPlayConnectionType_Unknown  0
#define kCarPlayConnectionType_Bonjour  1
#define kCarPlayConnectionType_iAP2     2

static void _handleRfcommData(int length, const uint8* ptr);
static void setMediaAccessControlAddress();
static void TearDowniAP2(ipod_t handle);
static ipod_t GetPreferenceiAP2Handle();

static IPODObserver                 observer;
static WifiConfigurationInfo        gWifiConfigurationInfo;
static iAP2Device                   gUsbDevice;
static iAP2Device                   gBluetoothDevice;
static iAP2Device                   gWiFiDevice;

static iAP2InterfaceDelegate            delegate;

static CarPlay2iAP2InterfaceDelegate    gC2iDelegate;
static pthread_t                        giAP2UsbThread;
static pthread_t                        giAP2BluetoothThread;
static pthread_t                        giAP2WiFiThread;
static pthread_t                        gUsbDetachProbeThread;
static int                              gCarPlayStarted = 0;
static int                              gLocationSwitch = 0;
static CarPlayConnectionType            gCarPlayConnectionType = kCarPlayConnectionType_Unknown;
static char Mac_addr[6];
//===========================================================================================================================
//  Global
//===========================================================================================================================

int     giAP2TransportType = kiAP2TransportType_Unknown;
char    gDeviceId[ 18 ];
int     gWireConflictWireless = 0;

//===========================================================================================================================
//  _GetUsbRoleStatus
//===========================================================================================================================

OSStatus _GetUsbRoleStatus(char* outRoleStatus) {
    OSStatus    err;
    FILE*       file;
    char        status[ 11] = { 0 };

    file = fopen(kUSBRoleStatusFile, "r");
    if (file == NULL) {
        ias_ulog_err(kLogLevelNotice, "open %s failed.\n", kUSBRoleStatusFile);
        err = kNotHandledErr;
        goto exit;
    }

    if (fgets(status, 11, file) == NULL) {
        ias_ulog_err(kLogLevelNotice, "fgets %s failed.\n", kUSBRoleStatusFile);
        err = kReadErr;
        goto exit;
    }

    err = kNoErr;
    memcpy(outRoleStatus, status, strlen(status));

exit:
    if (file) {
        fclose(file);
    }
    return (err);
}

//===========================================================================================================================
//  RoleSwitch
//===========================================================================================================================

#define IAP_GET_DEVICE_STATE               _IOW('z', 1, int)

static int _RoleSwitchIsComplete() {
    int fd = -1;
    int ret = -1;
    unsigned long ioctl_rdata = -1;

    fd = open("/dev/zjinnova_iap", O_RDWR);
    if (fd < 0) {
        ias_ulog_err(kLogLevelNotice, "open /dev/zjinnova_iap error = %s.\n", strerror(errno));
        return ret;
    }

    ret = ioctl(fd, IAP_GET_DEVICE_STATE, &ioctl_rdata);
//    ret = ioctl_rdata;
//    ias_ulog(kLogLevelNotice, "check iap deivce oline = %d, ret = %d \n", ioctl_rdata, ret);

//    if (ret) {
////        ias_ulog(kLogLevelNotice, "check iap deivce oline = %d, ret = %d \n", ioctl_rdata, ret);
//        goto exit;
//    }

exit:
    if (fd > 0) close(fd);
    return ioctl_rdata;
}

static int _roleSwitch() {
    int rc;
    int result = -1;
    int count = 0;
    int ret;

    struct libusb_device_handle*   deviceHandler = NULL;

    // 初始化 libusb
    rc = libusb_init(NULL);
    if (rc < 0) {
        ias_ulog_err(kLogLevelNotice, "Error initializing libusb: %s\n", libusb_error_name(rc));
        return -1;
    }

    // 根据 vid和pid打开指定的usb设备
    deviceHandler = libusb_open_device_with_vid_pid(NULL, 0x05ac, 0x12a8);
    if (!deviceHandler) {
        ias_ulog_err(kLogLevelNotice, "Error finding USB device.\n");
        goto exit;
    }

    // Detect if Apple device supports CarPlay in DeviceManager App, not in MiddleWare
    // _UsbVendorRequestGetSupportCapabilities();

    // _UsbVendorRequestHostModeSwitch();
    // 检查设备是否支持CarPlay
    // 0xC0, 0x53, 0x00, 0x00, 0x04
    unsigned char buf[4];
    libusb_control_transfer(deviceHandler, 0xC0, 0x53, 0x00, 0, buf, 0x04, 0);

    ret =  buf[0] & 0x01;
    if (ret == 1) {
        ias_ulog(kLogLevelNotice, "check support carplay is success. \n");
    }
    else {
        ias_ulog_err(kLogLevelNotice, "check support carplay is Failed. \n");
        goto exit;
    }

    // 发送控制命令给apple devices, 进行主从切换
    libusb_control_transfer(deviceHandler, 0x40, 0x51, 0x01, 0, NULL, 0, 0);

#if 0
    system("echo \"2\">/sys/devices/platform/soc/usbc0/otg_role");

#else
    result = handleCarPlaySwitchUsbModel(1);
#endif
    while (!(result = _RoleSwitchIsComplete()) && count++ < 400) {
        usleep(5000);
    }
exit:
    if (deviceHandler) {
        libusb_close(deviceHandler);
    }
    libusb_exit(NULL);

    deviceHandler = NULL;

    return result;
}

//===========================================================================================================================
//  _WireSetup
//===========================================================================================================================

static void* _UsbDetachProbeThread(void* inArg) {
    bool started = true;
    FILE* file;
    char state[ 16 ];

    while (started) {
        file = fopen(kUSBDetachProbeFile, "r");
        if (file == NULL) {
            ias_ulog_err(kLogLevelNotice, "open %s failed.\n", kUSBDetachProbeFile);
            break;
        }

        if (fgets(state, 16, file) == NULL) {
            ias_ulog_err(kLogLevelNotice, "fgets %s failed.\n", kUSBDetachProbeFile);
        }

        if (!strcmp(state, "suspended\n")) {
            system("echo usb_otg1_id=0 > /dev/hhhgpio");
            started = false;

            if (giAP2TransportType == kiAP2TransportType_HostUsb && gWireConflictWireless) {
                if (gCarPlayStarted) {
                    ias_ulog_err(kLogLevelNotice, "wire setup on wireless then end AirPlay session when plug out.\n");
                    gC2iDelegate.end_f();
                }
            }
            // Bad workaround. Sometimes AirPlaySession is blocked when iAP2 exits and usb plug-out, then simulate a message _AirPlayHandleSessionFinalized( AirPlay session ended )
            // Bad workaround. Optimized disconnect time.
            handleSessionFinalized();
            AirPlaySessionFinalized();

            ias_ulog_err(kLogLevelNotice, "Usb detached and role switch back.\n");
            break;
        }
        fclose(file);
        file = NULL;

        usleep(100000);
    }

    if (file) {
        fclose(file);
    }
    // return just for resolve complile error
    return inArg ;
}



static void* _iAP2UsbThread(void* inArg) {
    int             ret;
    OSStatus        err = kNoErr;

    ret = _roleSwitch();
//#if 1
//    ret = _roleSwitch();
//#else
//    ret = _platfrom_usb_role_switch();
//#endif

    if (ret != 1) {
//        system("cat /sys/devices/platform/soc/usbc0/usb_host");
        require_noerr(err = kCommandErr, exit);
    }

//  目前驱动不支持
//    ret = pthread_create( &gUsbDetachProbeThread, NULL, _UsbDetachProbeThread, NULL );
//    if( ret != 0 )
//    {
//        ias_ulog_err( kLogLevelNotice, "create UsbDetachProbeThread failed.\n");
//
//        require_noerr( err = kNotHandledErr, exit );
//    }
//    pthread_detach( gUsbDetachProbeThread );

    ret = ipodLibInitFun(IPOD_FUN_MODE_CARPLAY, IPOD_CONN_TYPE_USB_HOST);
    if (ret < 0) {
        ias_ulog_err(kLogLevelNotice, "open iAP2 device failed. ret = %d \n", ret);

        require_noerr(err = kOptionErr, exit);
    }

//    setMediaAccessControlAddress();

    memcpy(gUsbDevice.deviceId, "kiAP2TransportType_HostUsb", strlen("kiAP2TransportType_HostUsb"));
    gUsbDevice.transportType = kiAP2TransportType_HostUsb;

    ret = iap2Connect(&gUsbDevice.handle, gUsbDevice.deviceId);
    if (ret < 0) {
        ias_ulog_err(kLogLevelNotice, "init iAP2 device failed.\n");
        TearDowniAP2(gUsbDevice.handle);

        require_noerr(err = kOptionErr, exit);
    }

exit:
    if (err) {
        system("echo \"1\">/sys/devices/platform/soc/usbc0/otg_role");
        ias_ulog_err(kLogLevelNotice, "init iAP2 over Usb Status: %d.\n", err);
    }
    // return just for resolve complile error
    return inArg ;

}

static void _WireSetup() {
    int ret;

    ret = pthread_create(&giAP2UsbThread, NULL, _iAP2UsbThread, NULL);
    if (ret != 0) {
        ias_ulog_err(kLogLevelNotice, "create iAP2Thread over Usb failed.\n");
    }

    pthread_detach(giAP2UsbThread);
}

//===========================================================================================================================
//  _getBluetoothAddress
//===========================================================================================================================

int _getBluetoothAddress(char* outAddress) {
    FILE*   fd;
    char    buffer[ 64 ];
    char*   offset;
    char*   macAddress;

    fd = fopen(kBluetoothAddressFilePath, "r");
    if (fd == NULL) {
        ias_ulog_err(kLogLevelNotice, "open file deviceinfo.ini failed.\n");
        return -1;
    }
    while (fgets(buffer, 63, fd) != NULL) {
        offset = strstr(buffer, "bt_addr");
        if (offset != NULL) {
            macAddress = strstr(offset, "=");
            strcpy(outAddress, macAddress + 1);
            outAddress[ 17 ] = '\0';
            fclose(fd);
            return 0;
        }
    }

    fclose(fd);
    return 0;
}

//===========================================================================================================================
//  set MediaAccessControlAddress
//===========================================================================================================================

static void setMediaAccessControlAddress() {
    //uint8_t mac[6];
    //char bluetoothAddress[20] = {0};

    // _getBluetoothAddress( bluetoothAddress );
    //TextToHardwareAddress( bluetoothAddress, -1, 6, mac );

    notifyBtAddress(Mac_addr);
}

//===========================================================================================================================
//  _WirelessSetup
//===========================================================================================================================

static void* _iAP2Thread(void* inArg) {
    OSStatus    err = kNoErr;
    int         ret;

    ret = ipodLibInitFun(IPOD_FUN_MODE_CARPLAY, IPOD_CONN_TYPE_BLUETOOTH);
    if (ret < 0) {
        require_noerr(err = kOpenErr, exit);
    }
//
//    const char* ssid = "AndroidAP_5329";
//    const char* pwd = "12345678";
//    const char* channel = "0";

//    _sendWiFiInformation(ssid, pwd, 0, channel);
//    sendWifiInformation("AndroidAP_5310", "12345678", 0, NULL);
    wifiInformationSharing(gWifiConfigurationInfo);
    ipod_bt_spp_state_change(ipod_SppState_Connected);
    ipodRegistBtSppSendData(_handleRfcommData);
    setMediaAccessControlAddress();

    memcpy(gBluetoothDevice.deviceId, "kiAP2TransportType_Bluetooth", strlen("kiAP2TransportType_Bluetooth"));
    gBluetoothDevice.transportType = kiAP2TransportType_Bluetooth;

    ret = iap2Connect(&gBluetoothDevice.handle, gBluetoothDevice.deviceId);
    if (ret < 0) {
        TearDowniAP2(gBluetoothDevice.handle);
        require_noerr(err = kOptionErr, exit);
    }

exit:
    if (err) {
        ias_ulog_err(kLogLevelNotice, "init iAP2 over Bluetooth Status: %d.\n", err);
    }
    // return just for resolve complile error
    return inArg ;

}

static void _WirelessSetup() {
    int ret;

    ret = pthread_create(&giAP2BluetoothThread, NULL, _iAP2Thread, NULL);
    if (ret != 0) {
        ias_ulog_err(kLogLevelNotice, "create iAP2Thread over bluetooth failed.\n");
    }

    pthread_detach(giAP2BluetoothThread);
}

//===========================================================================================================================
//  _iAP2WiFiSetup
//===========================================================================================================================

// Device -> Accessory, read
void _sendWiFiData(const char* ptr, int length) {
    ipod_t      handle;

    handle = gWiFiDevice.handle;
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "sendWiFiData No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    sendWiFiData(handle, length, ptr);
}

static void* _iAP2WiFiThread(void* inArg) {
    OSStatus    err = kNoErr;
    int         ret;

    ret = ipodLibInitFun(IPOD_FUN_MODE_CARPLAY, IPOD_CONN_TYPE_WiFi);
    if (ret < 0) {
        require_noerr(err = kOpenErr, exit);
    }

    setWiFiDataCallback(gC2iDelegate.sendiAPMessage_f);
    setMediaAccessControlAddress();

    memcpy(gWiFiDevice.deviceId, "kiAP2TransportType_WiFi", strlen("kiAP2TransportType_WiFi"));
    gWiFiDevice.transportType = kiAP2TransportType_WiFi;

    ret = iap2Connect(&gWiFiDevice.handle, gWiFiDevice.deviceId);
    if (ret < 0) {
        TearDowniAP2(gWiFiDevice.handle);
        require_noerr(err = kOptionErr, exit);
    }

exit:
    if (err) {
        ias_ulog_err(kLogLevelNotice, "init iAP2 over Wi-Fi Status: %d.\n", err);
    }
    // return just for resolve complile error
    return inArg ;

}

static void _iAP2WiFiSetup() {
    int ret;

    ret = pthread_create(&giAP2WiFiThread, NULL, _iAP2WiFiThread, NULL);
    if (ret != 0) {
        ias_ulog_err(kLogLevelNotice, "create iAP2Thread over WiFi failed.\n");
    }

    pthread_detach(giAP2WiFiThread);
}

//===========================================================================================================================
//  iAP2ReceiverServerDelegate
//===========================================================================================================================

static void _iAP2SessionState(ipod_t handle, bool status) {
    ias_ulog(kLogLevelNotice, "iAP2SessionState: %d.\n", status);

    if (status == 0 || status == 2) {
        TearDowniAP2(handle);
    }
    else if (status == 1) {
        if (gUsbDevice.handle == handle) {
            gUsbDevice.started = 1;
            ias_ulog_err(kLogLevelNotice, "iAP2 Over Usb Started.\n");
        }
        else if (gBluetoothDevice.handle == handle) {
            gBluetoothDevice.started = 1;
            ias_ulog_err(kLogLevelNotice, "iAP2 Over Bluetooth Started.\n");
        }
        else if (gWiFiDevice.handle == handle) {
            gWiFiDevice.started = 1;
            ias_ulog_err(kLogLevelNotice, "iAP2 Over WiFi Started.\n");
        }
        else {
            ias_ulog_err(kLogLevelNotice, "iAP2SessionState iAP2 handle err.\n");
        }

        ias_ulog_err(kLogLevelNotice, "iAP2 is still running Usb: %d, Bluetooth: %d, WiFi: %d.\n", gUsbDevice.started,
                     gBluetoothDevice.started, gWiFiDevice.started);
    }
}

static ipod_t GetPreferenceiAP2Handle() {
    if (gUsbDevice.started == 1) {
        return gUsbDevice.handle;
    }
    else if (gWiFiDevice.started == 1) {
        return gWiFiDevice.handle;
    }
    else if (gBluetoothDevice.started == 1) {
        return gBluetoothDevice.handle;
    }
    else {
        ias_ulog_err(kLogLevelNotice, "There is no perferred iAP2 handle.\n");
        return -1;
    }
}

void    AirPlaySessionControlDisableBluetooth() {
    if (gBluetoothDevice.handle > 0) {
        TearDowniAP2(gBluetoothDevice.handle);
    }
}

static void _DeviceInformationUpdate(ipod_t handle,  const char* name) {
    ias_ulog(kLogLevelNotice, "DeviceInformationUpdate: %s.\n", name);

    handleDeviceInformationUpdate( name );
}

//===========================================================================================================================
//  PlaybackAttributes
//===========================================================================================================================

static void _PlaybackQueueIndex(ipod_t handle, uint32 index) {
//    char    MediaItemTitle[256];
//    char    MediaItemAlbumTitle[256];
//    char    MediaItemArtist[256];
//    int     MediaDuration = 0;
//
//    memset( MediaItemTitle, 0, 256 );
//    memset( MediaItemAlbumTitle, 0, 256 );
//    memset( MediaItemArtist, 0, 256 );
//
//    getPlayingTrackTitle( handle, MediaItemTitle );
//    getPlayingTrackAlbum( handle, MediaItemAlbumTitle );
//    getPlayingTrackArtist( handle, MediaItemArtist );
//    getPlayingTrackDuration( handle, &MediaDuration );
//
//    ias_ulog( kLogLevelNotice, "MediaItem MediaItemTitle: %s, MediaItemAlbumTitle: %s, MediaItemArtist: %s, MediaDuration: %d.\n",
//        MediaItemTitle, MediaItemAlbumTitle, MediaItemArtist, MediaDuration );

//    handleNowPlayingUpdateMediaItemAttributes( MediaItemTitle, MediaItemAlbumTitle, MediaItemArtist, MediaDuration );
}

static void _PlaybackStatus(ipod_t handle, int status) {
    ias_ulog(kLogLevelNotice, "PlaybackStatus: %d.\n", status);

    // handleNowPlayingUpdatePlaybackAttributes( status, -1, -1, -1 );
}

static void _PlaybackShuffleMode(ipod_t handle, int shuffle) {
    ias_ulog(kLogLevelNotice, "PlaybackShuffleMode: %d.\n", shuffle);
    //handleNowPlayingUpdatePlaybackAttributes( -1, shuffle, -1, -1 );
}

static void _PlaybackRepeatMode(ipod_t handle, int repeat) {
    ias_ulog(kLogLevelNotice, "PlaybackRepeatMode: %d.\n", repeat);
    //handleNowPlayingUpdatePlaybackAttributes( -1, -1, repeat, -1 );
}

static void _PlaybackElapsedTimeInMilliseconds(ipod_t handle, uint32 elapsedTime) {
    static uint8_t tick = 0;
    if (tick++ % 10 == 0) {
        ias_ulog(kLogLevelNotice, "PlaybackElapsedTimeInMilliseconds: %d.\n", elapsedTime);
    }

    //handleNowPlayingUpdatePlaybackAttributes( -1, -1, -1, elapsedTime );
}

//===========================================================================================================================
//  MediaLibraryUpdate
//===========================================================================================================================

static void _MediaLibraryUpdateProgress(ipod_t handle, uint8 progress) {
    if (progress == 0 || progress == 10 || progress == 20 || progress == 30 || progress == 40 || progress == 50
            || progress == 100) {
        ias_ulog(kLogLevelNotice, "MediaLibraryUpdateProgress: %d.\n", progress);
    }

    //handleMediaLibraryUpdateProgress( progress );
}

static void _ResetMusicList() {
    uint32      number;
    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "ResetMusicList No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    resetMusicList(handle, &number);
    ias_ulog(kLogLevelNotice, "ResetMusicList: %d.\n", number);
}

static void _SelectMusicList(int index) {
    uint32      number = 0;
    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "SelectMusicList No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    selectMusicList(handle, index, -1, &number);
    ias_ulog(kLogLevelNotice, "SelectMusicList: %d.\n", number);
}

static void _BackMusicList() {
    uint32      number = 0;
    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "BackMusicList No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    selectMusicList(handle, -1, -1, &number);
    ias_ulog(kLogLevelNotice, "BackMusicList: %d.\n", number);
}

static void _GetMusicList(int index, int count) {
    ias_ulog(kLogLevelNotice, "GetMusicList index: %d, count: %d.\n", index, count);

    ipod_t              handle;
    int                 ret;
    C_IPODMusicList     list;
    C_IPODMusicSource   source;
    char            senddata[ 3200 ] = {0};
    int             offset = 0;
    int             endIndex = 0;
    int             sourceNumber = 0;

    handle = GetPreferenceiAP2Handle();
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "GetMusicList No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    ret = getMusicList_c(handle, index, count, &list);

    if (ret >= 0) {
        // totalnum - 当前层级下记录总数
        senddata[ offset++ ] = ((list.totalnum >> 8) & 0xFF);
        senddata[ offset++ ] = (list.totalnum        & 0xFF);

        // categorytype - 当前层级的类型 {@see IPODCategoryType}
        senddata[ offset++ ] = list.categorytype;

        // start - 开始序号
        senddata[ offset++ ] = ((list.start >> 8) & 0xFF);
        senddata[ offset++ ] = (list.start        & 0xFF);

        // end - 结束序号
        endIndex = offset;
        senddata[ offset++ ] = ((list.end >> 8) & 0xFF);
        senddata[ offset++ ] = (list.end        & 0xFF);

        ias_ulog(kLogLevelNotice, "List totalnum: %d, categorytype: %d, start: %d, end: %d.\n", list.totalnum,
                 list.categorytype, list.start, list.end);

        for (int i = 0; i < list.size; i++) {
            memcpy(&source, &list.elements[ i ], sizeof(C_IPODMusicSource));
            sourceNumber++;

            // name
            int nameLength = strlen(source.name);

            if (offset + nameLength + 4 <= 3200) {
                // index
                senddata[ offset++ ] = ((source.index >> 8) & 0xFF);
                senddata[ offset++ ] = (source.index        & 0xFF);

                // name
                senddata[ offset++ ] = ((nameLength >> 8) & 0xFF);
                senddata[ offset++ ] = (nameLength        & 0xFF);
                memcpy(&senddata[offset], source.name, nameLength);

                offset += nameLength;
                ias_ulog(kLogLevelNotice, "List Source index: %d, name: %s.\n", source.index, source.name);
            }
            else {
                ias_ulog(kLogLevelNotice, "List request once is too large offset: %d.\n", offset);
                // end - 结束序号
                senddata[ endIndex++ ] = (((list.start + sourceNumber) >> 8) & 0xFF);
                senddata[ endIndex++ ] = ((list.start + sourceNumber)        & 0xFF);
                break;
            }
            memset(source.name, '\0', 128);
            source.index = 0;
        }

        //handlePlayListInfo( senddata, offset );
    }
}

static void _GetMusicPlayingList(int index, int count) {
    ias_ulog(kLogLevelNotice, "GetMusicPlayingList index: %d, count: %d.\n", index, count);

    ipod_t              handle;
    int                 ret;
    C_IPODMusicList     list;
    C_IPODMusicSource   source;
    char            senddata[ 3200 ] = {0};
    int             offset = 0;
    int             endIndex = 0;
    int             sourceNumber = 0;

    handle = GetPreferenceiAP2Handle();
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "GetMusicPlayingList No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    ret = getMusicPlayingList(handle, index, count, &list);

    if (ret >= 0) {
        // totalnum - 当前层级下记录总数
        senddata[ offset++ ] = ((list.totalnum >> 8) & 0xFF);
        senddata[ offset++ ] = (list.totalnum        & 0xFF);

        // categorytype - 当前层级的类型 {@see IPODCategoryType}
        senddata[ offset++ ] = list.categorytype;

        // start - 开始序号
        senddata[ offset++ ] = ((list.start >> 8) & 0xFF);
        senddata[ offset++ ] = (list.start        & 0xFF);

        // end - 结束序号
        endIndex = offset;
        senddata[ offset++ ] = ((list.end >> 8) & 0xFF);
        senddata[ offset++ ] = (list.end        & 0xFF);

        ias_ulog(kLogLevelNotice, "PlayList totalnum: %d, categorytype: %d, start: %d, end: %d.\n", list.totalnum,
                 list.categorytype, list.start, list.end);

        for (int i = 0; i < list.size; i++) {
            memcpy(&source, &list.elements[ i ], sizeof(C_IPODMusicSource));
            sourceNumber++;

            // name
            int nameLength = strlen(source.name);

            if (offset + nameLength + 4 <= 3200) {
                // index
                senddata[ offset++ ] = ((source.index >> 8) & 0xFF);
                senddata[ offset++ ] = (source.index        & 0xFF);

                // name
                senddata[ offset++ ] = ((nameLength >> 8) & 0xFF);
                senddata[ offset++ ] = (nameLength        & 0xFF);
                memcpy(&senddata[offset], source.name, nameLength);

                offset += nameLength;
                ias_ulog(kLogLevelNotice, "PlayList Source index: %d, name: %s.\n", source.index, source.name);
            }
            else {
                ias_ulog(kLogLevelNotice, "PlayList request once is too large offset: %d.\n", offset);
                // end - 结束序号
                senddata[ endIndex++ ] = (((list.start + sourceNumber) >> 8) & 0xFF);
                senddata[ endIndex++ ] = ((list.start + sourceNumber)        & 0xFF);
                break;
            }
            memset(source.name, '\0', 128);
            source.index = 0;
        }

        // handlePlayListInfo( senddata, offset );
    }
}

static void _SelectMusicPlayingList(int index) {
    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "SelectMusicPlayingList No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    selectMusicPlayingList(handle, index);
    ias_ulog(kLogLevelNotice, "SelectMusicPlayingList: %d.\n", index);
}

static void _MediaPlayingListUpdate(ipod_t handle, int index) {
    ias_ulog(kLogLevelNotice, "MediaPlayingListUpdate index: %d.\n", index);

    //handleMediaPlayListUpdate( index );
}

static void _PlaybackQueueListAvailable(ipod_t handle, bool available) {
    ias_ulog(kLogLevelNotice, "PlaybackQueueListAvailable available: %d.\n", available);

    // handlePlaybackQueueListAvailable( available );
}

//===========================================================================================================================
//  NowPlayingUpdate - MediaItem
//===========================================================================================================================

static void _MediaItemArtworkFileTransferIdentifier(ipod_t handle, const char* path) {
    ias_ulog(kLogLevelNotice, "MediaItemArtworkFileTransferIdentifier: %s.\n", path);

    //handleMediaItemArtworkFileTransferIdentifier( path );
}

static void _CallStateUpdate(ipod_t handle, CallStateInformation* callState) {
    ias_ulog(kLogLevelNotice, "CallStateUpdate RemoteID: %s, DisplayName: %s, Status: %d, Direction: %d.\n",
             callState->call_state_phone_number, callState->call_state_network_sent_caller_name, callState->call_state_status,
             callState->call_state_direction);

    //handleCallStateUpdate( callState->call_state_phone_number, callState->call_state_network_sent_caller_name,
    //    callState->call_state_status, callState->call_state_direction );
}

static void _LocationSwitch(ipod_t handle, int status, CIAP2LocationType* type) {
    ias_ulog(kLogLevelNotice, "LocationSwitch: %d.\n", status);

    gLocationSwitch = status;
}

static void _IdentifierNotificationUpdated(ipod_t handle,  CDeviceTransportNotification* di) {
    ias_ulog(kLogLevelNotice, "BluetoothTransportIdentifier: %s, USBTransportIdentifier: %s\n",
             di->BluetoothTransportIdentifier, di->USBTransportIdentifier);

    //handleIdentifierNotificationUpdated( di->BluetoothTransportIdentifier, di->USBTransportIdentifier );

    memset(gDeviceId, 0, 18);
    memcpy(gDeviceId, di->BluetoothTransportIdentifier, strlen(di->BluetoothTransportIdentifier));

    if (gC2iDelegate.controllerEqual_f() == 0) {
        ias_ulog_err(kLogLevelNotice, "Global CarPlayControllerRef doesn't equal current iAP2 deviceID, so not upload.\n");
        return;
    }
    BonjourCarPlayAvailability();
}

//===========================================================================================================================
//  RouteGuidance
//===========================================================================================================================

void SecondsToHM(int64_t inSeconds, int* outHours, int* outMinutes) {
    int seconds;
    int hours, minutes;

    hours   = inSeconds / kSecondsPerHour;
    seconds = inSeconds % kSecondsPerHour;
    minutes = seconds   / kSecondsPerMinute;
    seconds = seconds   % kSecondsPerMinute;

    if (outHours) {
        *outHours   = hours;
    }
    if (outMinutes) {
        *outMinutes = minutes;
    }
}

static void _RouteGuidanceUpdate(ipod_t handle, RouteGuidanceUpdate* rgu) {
    int     hours, minutes;
    int64_t seconds = rgu->timeRemainingToDestination;

    SecondsToHM(seconds, &hours, &minutes);

    // ias_ulog( kLogLevelNotice, "\n - RouteGuidanceState: %d, \n - ManeuverState: %d, \n - CurrentRoadName: %s, "
    //     "\n - DestinationName: %s, \n - DistanceRemainingDisplayStr: %s, \n - DistanceRemainingDisplayUnits: %d, "
    //     "\n - DistanceToNextManeuverDisplayStr: %s, \n - DistanceToNextManeuverDisplayUnits: %d, \n - TimeRemainingToDestination hours: %d, minutes: %d.\n",
    //     rgu->routeGuidanceState, rgu->maneuverState, rgu->currentRoadName, rgu->destinationName,
    //     rgu->distanceRemainingDisplayStr, rgu->distanceRemainingDisplayUnits,
    //     rgu->distanceToNextManeuverDisplayStr, rgu->distanceToNextManeuverDisplayUnits,
    //     hours, minutes );

    // handleRouteGuidanceUpdate( rgu->routeGuidanceState, rgu->maneuverState, rgu->currentRoadName, rgu->destinationName, rgu->distanceRemainingDisplayStr, rgu->distanceRemainingDisplayUnits,
    //      rgu->distanceToNextManeuverDisplayStr, rgu->distanceToNextManeuverDisplayUnits, hours, minutes );
}

static void _RouteGuidanceManeuverInformation(ipod_t handle, RouteGuidanceManeuverUpdate* rgmu) {
    ias_ulog(kLogLevelNotice, "maneuverType: %d, afterManeuverRoadName: %s, drivingSide: %d.\n", rgmu->maneuverType,
             rgmu->afterManeuverRoadName, rgmu->drivingSide);

    //handleRouteGuidanceManeuverInformation( rgmu->maneuverType, rgmu->afterManeuverRoadName, rgmu->drivingSide );
}

//===========================================================================================================================
//  Out-of-Band Bluetooth Pairing
//===========================================================================================================================

static void _OOBBTPairingLinkKeyInformation(ipod_t handle, OOBLinkKeyInformation* linkKey) {
    // ias_ulog( kLogLevelNotice, "linkKey: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n",
    //     linkKey->link_key[0], linkKey->link_key[1], linkKey->link_key[ 2], linkKey->link_key[ 3], linkKey->link_key[ 4], linkKey->link_key[ 5], linkKey->link_key[ 6], linkKey->link_key[ 7],
    //     linkKey->link_key[8], linkKey->link_key[9], linkKey->link_key[10], linkKey->link_key[11], linkKey->link_key[12], linkKey->link_key[13], linkKey->link_key[14], linkKey->link_key[15] );
    //handleOobBtPairingLinkKeyInformation( linkKey->link_key, linkKey->link_key_len, linkKey->mac_address );
}

//===========================================================================================================================
//  RFCOMM data from accessory to device
//===========================================================================================================================

static void _handleRfcommData(int length, const uint8* ptr) {
    ias_ulog(kLogLevelNotice, "iAP2_AppStub.c: _handleRfcommData\n");

    handleRfcommData( ptr, length );
}

static void _WirelessCarPlayUpdate(ipod_t handle,  int available) {
    if (giAP2TransportType == kiAP2TransportType_HostUsb) {
        ias_ulog(kLogLevelNotice, "WireCarPlay ignore WirelessCarPlayUpdate.\n");
        return;
    }
    ias_ulog(kLogLevelNotice, "WirelessCarPlayUpdate Available: %d\n", available);
    //if( available == 0 )    handleCarPlayAvailable( 0 );
}

void BonjourCarPlayAvailability() {
    if (gCarPlayStarted == 1) {
        ias_ulog(kLogLevelNotice, "BonjourCarPlayAvailability CarPlay has already been started.\n");
        return;
    }
    if (giAP2TransportType == kiAP2TransportType_HostUsb) {
        ias_ulog_err(kLogLevelNotice, "BonjourCarPlayAvailability over Usb, so not upload and connect directly.\n");
        gC2iDelegate.carPlayStartBonjour_f();
        return;
    }
    ias_ulog_err(kLogLevelNotice, "Global CarPlayControllerRef equal current iAP2 deviceID, so upload.\n");

    gCarPlayConnectionType = kCarPlayConnectionType_Bonjour;
    handleCarPlayAvailable( 1 );
}

void _CarPlayAvailability(ipod_t handle, CarPlayAvailInfo* info) {
    ias_ulog(kLogLevelNotice, "CarPlayAvailability WireAvailable: %d, WirelessAvailable: %d.\n", info->wireAvailable,
             info->wirelessAvailable);

    if (gCarPlayStarted == 1) {
        ias_ulog(kLogLevelNotice, "CarPlayAvailability CarPlay has already been started.\n");
        return;
    }

    if (giAP2TransportType == kiAP2TransportType_HostUsb) {
        ias_ulog_err(kLogLevelNotice, "CarPlayAvailability over Usb, so not upload and connect directly.\n");
        if (info->wireAvailable == 1) {
            iap2CarPlayStartSession(handle, 0);
        }
    }

    gCarPlayConnectionType = kCarPlayConnectionType_iAP2;
    if (giAP2TransportType == kiAP2TransportType_Bluetooth) {
        //handleCarPlayAvailable( info->wirelessAvailable );
    }
}

//===========================================================================================================================
//  _setup
//===========================================================================================================================

static void _setup(
    int             type,
    uint16_t        vendorId,
    uint16_t        productId,
    const char*      address,
    int             same) {
    ias_ulog_err(kLogLevelNotice, "iAP2 setup type: %d, same device: %d.\n", type, same);

    if (!gCarPlayStarted) {
        if (gBluetoothDevice.handle > 0) {
            TearDowniAP2(gBluetoothDevice.handle);
        }
    }

    if (gCarPlayStarted && type == kiAP2TransportType_HostUsb) {
        ias_ulog_err(kLogLevelNotice, "CarPlay has already started and not support switch to CarPlay over USB until now.\n");
        return;
    }

    observer.on_connect_status_changed              = _iAP2SessionState;
    observer.on_device_information_changed          = _DeviceInformationUpdate;
    observer.on_device_transportid_changed          = _IdentifierNotificationUpdated;
    observer.on_shuffle_mode_changed                = _PlaybackShuffleMode;
    observer.on_repeat_mode_changed                 = _PlaybackRepeatMode;
    observer.on_track_position_changed              = _PlaybackElapsedTimeInMilliseconds;
    observer.on_playback_state_changed              = _PlaybackStatus;
    observer.on_track_changed                       = _PlaybackQueueIndex;
    observer.on_album_artwark_changed               = _MediaItemArtworkFileTransferIdentifier;
    observer.on_media_update_progress               = _MediaLibraryUpdateProgress;
    observer.on_call_state_changed                  = _CallStateUpdate;
    observer.on_location_enable_changed             = _LocationSwitch;
    observer.on_wireless_carplay_update             = _WirelessCarPlayUpdate;
    observer.on_route_guidance_changed              = _RouteGuidanceUpdate;
    observer.on_route_guidance_maneuver_changed     = _RouteGuidanceManeuverInformation;
    observer.on_device_link_key                     = _OOBBTPairingLinkKeyInformation;
    observer.on_carplay_availability                = _CarPlayAvailability;
    observer.on_media_playing_list_changed          = _MediaPlayingListUpdate;
    observer.on_playback_queuelist_available        = _PlaybackQueueListAvailable;

    registerObserver(&observer);

    giAP2TransportType = type;

    if (type == kiAP2TransportType_HostUsb) {
        _WireSetup();
    }
    else if (type == kiAP2TransportType_Bluetooth) {
        _WirelessSetup();
    }
    else if (type == kiAP2TransportType_WiFi) {
        _iAP2WiFiSetup();
    }
}

void iAP2OverCarPlaySetup() {
    _setup(kiAP2TransportType_WiFi, 0, 0, NULL, 0);
}

//===========================================================================================================================
//  TearDowniAP2
//===========================================================================================================================

static void TearDowniAP2(ipod_t handle) {
    if (gUsbDevice.handle == handle) {
        gUsbDevice.started = 0;
        gUsbDevice.handle = -1;

        iap2Disconnect(&handle);
        ias_ulog_err(kLogLevelNotice, "finalize iAP2 Over Usb.\n");
    }
    else if (gBluetoothDevice.handle == handle) {
        gBluetoothDevice.started = 0;
        gBluetoothDevice.handle = -1;

        iap2Disconnect(&handle);
        ias_ulog_err(kLogLevelNotice, "finalize iAP2 Over Bluetooth.\n");
    }
    else if (gWiFiDevice.handle == handle) {
        gWiFiDevice.started = 0;
        gWiFiDevice.handle = -1;

        iap2Disconnect(&handle);
        ias_ulog_err(kLogLevelNotice, "finalize iAP2 Over WiFi.\n");
    }
    else {
        ias_ulog_err(kLogLevelNotice, "finalize iAP2 handle err.\n");
    }

    ias_ulog_err(kLogLevelNotice, "iAP2 is still running Usb: %d, Bluetooth: %d, WiFi: %d.\n", gUsbDevice.started,
                 gBluetoothDevice.started, gWiFiDevice.started);
}

//===========================================================================================================================
//  build and send the Custom NMEA Sentences (PASCD)
//===========================================================================================================================

static void _sendPascdSentence(char transmissionState, int sampleCount, double* speed) {
    char        PASCDNmeaSentences[128];
    char        NmeaSentences[128];
    float       timeOffset_i;
    double      speed_i;
    char        Checksum = 0;
    ipod_t      handle;

    if (!gLocationSwitch) {
        return;
    }

    handle = GetPreferenceiAP2Handle();
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "sendPascdSentence No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    struct timeval  now;
    gettimeofday(&now, NULL);

    sprintf(PASCDNmeaSentences, "PASCD,%ld.%.3ld,C,%c,0,%d", now.tv_sec, now.tv_usec / 1000, transmissionState,
            sampleCount);

    for (int i = 0; i < sampleCount; i++) {
        timeOffset_i = (float)i / (float)sampleCount;
        // convert km/h to m/s
        speed_i = speed[i] / 3.6;
        sprintf(PASCDNmeaSentences, "%s,%.2f,%.3f", PASCDNmeaSentences, timeOffset_i, speed_i);
    }

    for (int i = 0; i < strlen(PASCDNmeaSentences); i++) {
        Checksum = Checksum ^ PASCDNmeaSentences[i];
    }

    sprintf(NmeaSentences, "$%s*%X", PASCDNmeaSentences, Checksum);

    setLocationInformation(handle, NmeaSentences);
}

//===========================================================================================================================
//  send the NMEA Sentences
//===========================================================================================================================

static void _sendNmeaSentence(const char* nmeaSentence) {
    ipod_t      handle;

    if (gLocationSwitch) {
        handle = GetPreferenceiAP2Handle();
        if (handle == -1) {
            ias_ulog_err(kLogLevelNotice, "sendNmeaSentence No Perferred iAP2 handle: %d.\n", handle);
            return;
        }
        setLocationInformation(handle, nmeaSentence);
    }
}

//===========================================================================================================================
//  RFCOMM data from device to accessory
//===========================================================================================================================
static void _sendRfcommData(char* ptr, int length) {
    // ias_ulog_err( kLogLevelNotice, "RfcommData ( Device -> Accessory ) length: %d.\n", length );

    ipod_t      handle;

    handle = gBluetoothDevice.handle;
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "sendRfcommData No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    ipod_BT_spp_data_pro(handle, length, ptr);
}

//===========================================================================================================================
//  _sendWiFiInformation
//===========================================================================================================================

static void _sendWiFiInformation(
    const char*     ssid,
    const char*     passphrase,
    int             securityType,
    int8            channel) {
    ias_ulog(kLogLevelNotice, "sendWiFiInformation SSID: %s, Passphrase: %s\n", ssid, passphrase);

    memset(gWifiConfigurationInfo.wifi_ssid, 0, 128);
    memset(gWifiConfigurationInfo.pass_phrase, 0, 128);

    memcpy(gWifiConfigurationInfo.wifi_ssid, ssid, strlen(ssid));
    memcpy(gWifiConfigurationInfo.pass_phrase, passphrase, strlen(passphrase));

    gWifiConfigurationInfo.security_type = 2; // securityType;
    gWifiConfigurationInfo.channel = 129;//channel;
}

//===========================================================================================================================
//  _SetMacAddr_
//===========================================================================================================================
static void _SetMacAddr(char* addr) {
    memcpy(Mac_addr,addr,6);
}
//===========================================================================================================================
//  Out-of-Band Bluetooth Pairing
//===========================================================================================================================

static void _sendOobBtPairingCompletionInformation(int resultCode) {
    ias_ulog(kLogLevelNotice, "sendOobBtPairingCompletionInformation resultCode: %d\n", resultCode);

    ipod_t      handle;

    handle = GetPreferenceiAP2Handle();
    if (handle == -1) {
        ias_ulog_err(kLogLevelNotice, "sendOobBtPairingCompletionInformation No Perferred iAP2 handle: %d.\n", handle);
        return;
    }

    iap2OOBBTPairingCompletionInformation(handle, resultCode);
}

//===========================================================================================================================
//  _sendCarPlayStartSession
//===========================================================================================================================

static void _sendCarPlayStartSession(int allow) {
    ias_ulog_err(kLogLevelNotice, "sendCarPlayStartSession allow: %d\n", allow);

    ipod_t      handle;

    if (allow == 1) {
        if (gCarPlayConnectionType == kCarPlayConnectionType_Bonjour) {
            gC2iDelegate.carPlayStartBonjour_f();
        }
        if (gCarPlayConnectionType == kCarPlayConnectionType_iAP2) {
            handle = GetPreferenceiAP2Handle();
            if (handle == -1) {
                ias_ulog_err(kLogLevelNotice, "sendCarPlayStartSession No Perferred iAP2 handle: %d.\n", handle);
                return;
            }
            iap2CarPlayStartSession(handle, 1);
        }
    }
    else {
        AirPlaySessionFinalized();
    }
}

//===========================================================================================================================
//  AirPlaySessionCreated
//===========================================================================================================================

void    AirPlaySessionCreated() {
    gCarPlayStarted = 1;
}

//===========================================================================================================================
//  AirPlaySessionFinalized
//===========================================================================================================================

void    AirPlaySessionFinalized() {
    giAP2TransportType = kiAP2TransportType_Unknown;
    gCarPlayStarted = 0;
    gLocationSwitch = 0;
    gWireConflictWireless = 0;
    gCarPlayConnectionType = kCarPlayConnectionType_Unknown;
    memset(gDeviceId, 0, 18);

    if (gUsbDevice.handle > 0) {
        TearDowniAP2(gUsbDevice.handle);
    }
    if (gWiFiDevice.handle > 0) {
        TearDowniAP2(gWiFiDevice.handle);
    }
    if (gBluetoothDevice.handle > 0) {
        TearDowniAP2(gBluetoothDevice.handle);
    }

    handleCarPlayExit();
}

//===========================================================================================================================
//  CarPlay2iAP2InterfaceSetDelegate
//===========================================================================================================================
#if  1
void CarPlay2iAP2InterfaceSetDelegate(const CarPlay2iAP2InterfaceDelegate* inDelegate) {
    gC2iDelegate = *inDelegate;
}
#endif
#if 1
void iAP2InterfaceInitial() {
    initialize();
#if 1
    iAP2InterfaceDelegateInit(&delegate);
    delegate.setup_f                    = _setup;
    delegate.sendNmeaSentence_f         = _sendNmeaSentence;
    delegate.sendPascdSentence_f        = _sendPascdSentence;
    delegate.sendRfcommData_f           = _sendRfcommData;
    delegate.sendWiFiInformation_f      = _sendWiFiInformation;
    delegate.sendCarPlayStartSession_f  = _sendCarPlayStartSession;
    delegate.sendOobBtPairingCompletionInformation_f        = _sendOobBtPairingCompletionInformation;
    delegate.sendResetMusicList_f       = _ResetMusicList;
    delegate.sendSelectMusicList_f      = _SelectMusicList;
    delegate.sendBackMusicList_f        = _BackMusicList;
    delegate.sendGetMusicList_f         = _GetMusicList;
    delegate.sendGetMusicPlayList_f     = _GetMusicPlayingList;
    delegate.sendSelectMusicPlayList_f  = _SelectMusicPlayingList;
    delegate.setMacAddr_f               = _SetMacAddr;
    iAP2InterfaceSetDelegate(&delegate);
#endif
}
#endif
